import { UserModel } from "@/api/model/user.model.ts";
import { RoomInfo } from "@/pages/room/model.ts";

interface EventDetails {}
interface ClientEventDetails extends EventDetails {}
interface ServerEventDetails extends EventDetails {}

export enum VideoState {
    PLAY = 1,
    PAUSE = 2,
    STOP = 3
}

// region client

interface VideoStateClientEventDetails extends ClientEventDetails {
    state: VideoState
}

interface VideoSeekedClientEventDetails extends ClientEventDetails {
    /**
     * 毫秒
     */
    time: number
}

interface MessageClientEventDetails extends ClientEventDetails {
    message: string
}

interface VideoSpeedClientEventDetails extends ClientEventDetails {
    speed: number
}

interface VideoChangeClientEventDetails extends ClientEventDetails {
    url: string
}

interface RoomDissolveClientEventDetails extends ClientEventDetails {
    rid: string
}

interface RoomModifyNameClientEventDetails extends ClientEventDetails {
    name: string
}

interface RoomModifyPasswordClientEventDetails extends ClientEventDetails {
    pwd: string
}

// endregion


// region server

export interface RoomInitServerEventDetails extends ServerEventDetails {
    room: RoomInfo
}

export interface UserJoinServerEventDetails extends ServerEventDetails {
    user: UserModel
}

interface UserLeaveServerEventDetails extends ServerEventDetails {
    user: number
}

interface VideoStateServerEventDetails extends ServerEventDetails {
    operator: number,
    state: VideoState
}

interface VideoSeekedServerEventDetails extends ServerEventDetails {
    operator: number,
    /**
     * 毫秒数
     */
    time: number
}

interface VideoSpeedServerEventDetails extends ServerEventDetails {
    operator: number,
    speed: number
}

interface MessageServerEventDetails extends ServerEventDetails {
    sender: number,
    message: string
}

interface VideoChangeServerEventDetails extends ServerEventDetails {
    url: string
}

// endregion

interface ClientEventDetailsMap {
    "message": MessageClientEventDetails,
    "video.state": VideoStateClientEventDetails,
    "video.seeked": VideoSeekedClientEventDetails,
    "video.speed": VideoSpeedClientEventDetails,
    "video.change": VideoChangeClientEventDetails,
    "room.dissolve": RoomDissolveClientEventDetails,
    "room.modify.name": RoomModifyNameClientEventDetails,
    "room.modify.password": RoomModifyPasswordClientEventDetails
}

interface ServerEventDetailsMap {
    "room.init": RoomInitServerEventDetails,
    "user.join": UserJoinServerEventDetails,
    "user.leave": UserLeaveServerEventDetails,
    "message": MessageServerEventDetails,
    "video.state": VideoStateServerEventDetails,
    "video.seeked": VideoSeekedServerEventDetails,
    "video.speed": VideoSpeedServerEventDetails,
    "video.change": VideoChangeServerEventDetails
}


export interface ClientEvent<K extends keyof ClientEventDetailsMap> {
    type?: K
    details: ClientEventDetailsMap[K]
}

export interface ServerEvent<K extends keyof ServerEventDetailsMap> {
    type: K
    details: ServerEventDetailsMap[K]
}


export type ServerEventListener<K extends keyof ServerEventDetailsMap> = (details: ServerEventDetailsMap[K], event: ServerEvent<K>) => void


export class WS {
    private catchAction: ((e: any) => void) | null = null

    private url: string;
    private websocket: WebSocket | null = null;
    private listenerMap = new Map<string, ServerEventListener<any>[]>();
    private nativeListenerMap = new Map<string, ((this: WebSocket, ev: any) => void)[]>();
    constructor(url: string, params?: { [p: string]: any | any[] }) {
        const protocol = window.location.protocol.startsWith("https") ? "wss" : "ws";
        const urlObj = new URL(import.meta.env.APP_WS_PREFIX + url, `${protocol}://${window.location.host}${window.location.pathname}`);
        if (params) {
            for (const param in params) {
                const paramValue = params[param];
                if (paramValue === void 0 || paramValue === null) {
                    continue;
                }
                if (paramValue instanceof Array) {
                    for (let value of paramValue) {
                        urlObj.searchParams.append(param, value.toString());
                    }
                } else {
                    urlObj.searchParams.append(param, paramValue.toString());
                }
            }
        }
        this.url = urlObj.toString();
    }

    connect(): WS {
        this.websocket?.close();
        const ws = new WebSocket(this.url);
        this.websocket = ws;
        ws.addEventListener("open", () => {
            ws.addEventListener("message", (ev) => {
                const message = ev.data;
                if (typeof message === "string") {
                    this.handleTextMessage(message);
                }
            });
        });
        // 添加原生监听器
        for (const event of this.nativeListenerMap.keys()) {
            this.nativeListenerMap.get(event)?.forEach(listener => {
                ws.addEventListener(event, listener);
            });
        }
        return this;
    }

    disconnect(): WS {
        this.websocket?.close();
        this.websocket = null;
        return this;
    }

    reconnect() {
        this.disconnect();
        this.connect();
    }

    private handleTextMessage(message: string) {
        try {
            const object = JSON.parse(message) as ServerEvent<any>;
            if (typeof object.type === "string" && object.details && typeof object.details === "object") {
                const listeners = this.listenerMap.get(object.type);
                if (listeners?.length) {
                    for (const listener of listeners) {
                        listener(object.details, object);
                    }
                }
            }
        } catch (e: any) {
            if (this.catchAction) {
                this.catchAction(e);
            } else {
                throw e;
            }
        }
    }

    catch(callback: (e: any) => void): WS {
        this.catchAction = callback;
        return this;
    }

    on<K extends keyof ServerEventDetailsMap>(key: K, listener: ServerEventListener<K>): WS {
        const listeners = this.listenerMap.get(key);
        if (listeners) {
            listeners.push(listener);
        } else {
            this.listenerMap.set(key, [listener]);
        }
        return this;
    }

    off<K extends keyof ServerEventDetailsMap>(key: K, listener: ServerEventListener<K>): WS {
        const listeners = this.listenerMap.get(key);
        if (listeners?.length) {
            const index = listeners.findIndex(v => v === listener);
            if (index !== -1) {
                listeners.splice(index, 1);
            }
        }
        return this;
    }

    emit<K extends keyof ClientEventDetailsMap>(key: K, event: ClientEvent<K>): WS {
        try {
            if (this.websocket) {
                if (!event.type) {
                    event.type = key;
                }
                this.websocket.send(JSON.stringify(event));
            }
        } catch (e: any) {
            if (this.catchAction) {
                this.catchAction(e);
            } else {
                throw e;
            }
        }
        return this;
    }

    addEventListener<K extends keyof WebSocketEventMap>(event: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => void): WS {
        const listeners = this.nativeListenerMap.get(event);
        if (listeners) {
            listeners.push(listener);
        } else {
            this.nativeListenerMap.set(event, [listener]);
        }
        return this;
    }
}